//
//  paramter.hpp
//  utilities
//
//  Created by Sam Jaffe on 2/4/15.
//  Copyright (c) 2015 Sam Jaffe. All rights reserved.
//

#pragma once

#include <vector>
#include <functional>
#include <utility>
#include <tuple>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>

#include "exception.hpp"

namespace options {
    class parameter_base {
    public:
        typedef std::vector<std::string> names;
        virtual ~parameter_base() {}
        
        void parse(std::string* args) { parse_(args); };
        inline int arity() const { return arity_; }
        inline const names& get_names() const { return names_; }
    protected:
        virtual void parse_(std::string* args) = 0;
        parameter_base(const names& alias, int arity) : names_(alias), arity_(arity) { }
        
        names names_;
        int arity_ = -1;
        bool visited = false;
    };
    
    class parameter : public parameter_base {
    public:
        typedef parameter_base::names names;
        typedef std::function<void(std::string*)> parser;
    public:
        template <class type>
        parameter(const names& alias, type& ref);
        template <class T1, class T2>
        parameter(const names& alias, std::pair<T1, T2>& ref);
        template <class... Ts>
        parameter(const names& alias, std::tuple<Ts...>& ref);
        parameter(const names& alias, int arity, parser parse);
    protected:
        inline void parse_(std::string* args) { parser_(args); }
    private:
        void validate_arity_or_throw();
        parser parser_;
    };
    
    class vparameter : public parameter_base {
    public:
        typedef parameter_base::names names;
        typedef std::function<int(std::string*)> parser;
        
    public:
        vparameter(const names& alias, parser parse);
    protected:
        inline void parse_(std::string* args) { arity_ = parser_(args); }
    private:
        parser parser_;
    };
    
    namespace {
        inline std::function<void(std::string*)> parser_no_args(bool& ref) {
            return [&ref](std::string* args) {ref = true;};
        }
        
        template <class type>
        inline std::function<void(std::string*)> parser_one_arg(type& ref) {
            return [&ref](std::string* args) {
                ref = boost::lexical_cast<type>(*args);
            };
        }
        
        template <class type>
        inline std::function<void(std::string*)> create_parser(type& ref, int arity) {
            if (arity != 1) {
                throw arity_mismatch_internal();
            } else {
                return parser_one_arg(ref);
            }
        }
        
        inline std::function<void(std::string*)> create_parser(bool& ref, int arity) {
            if (arity == -1) {
                return parser_no_args(ref);
            } else if (arity == 1) {
                return parser_one_arg(ref);
            } else {
                throw arity_mismatch_internal();
            }
        }
        
        template <class T1, class T2>
        inline std::function<void(std::string*)> create_parser(std::pair<T1, T2>& ref, int arity) {
            if (arity != 2) {
                throw arity_mismatch_internal();
            } else {
                return [&ref](std::string* args) {
                    ref = std::make_pair(boost::lexical_cast<T1>(args[0]),
                                         boost::lexical_cast<T2>(args[1]));;
                };
            }
        }
        
        template <size_t i, class Tp>
        struct apply_tuple {
            inline static void apply(Tp& ref, std::string* args) {
                apply_tuple<i-1, Tp>::apply(ref, args);
                std::get<i>(ref) = boost::lexical_cast<typename std::tuple_element<i, Tp>::type>(args[i]);
            }
        };
        
        template <class Tp>
        struct apply_tuple<0, Tp> {
            inline static void apply(Tp& ref, std::string* args) {
                std::get<0>(ref) = boost::lexical_cast<typename std::tuple_element<0, Tp>::type>(args[0]);
            }
        };
        
        template <class... Ts>
        inline std::function<void(std::string*)> create_parser(std::tuple<Ts...>& ref, int arity) {
            if (arity != std::tuple_size<std::tuple<Ts...>>::value) {
                throw arity_mismatch_internal();
            } else {
                return [&ref](std::string* args) {
                    apply_tuple<std::tuple_size<std::tuple<Ts...>>::value - 1, std::tuple<Ts...>>::apply(ref, args);
                };
            }
        }
    }
    
    template <class type>
    parameter::parameter(const names& alias, type& ref) : parameter_base(alias, 1) {
        parser_ = create_parser(ref, arity_);
    }
    
    template <>
    parameter::parameter(const names& alias, bool& ref) : parameter_base(alias, 0), parser_(parser_no_args(ref)) {
        
    }
    
    template <class T1, class T2>
    parameter::parameter(const names& alias, std::pair<T1, T2>& ref) : parameter_base(alias, 2) {
        parser_ = create_parser(ref, arity_);
    }
    
    template <class... Ts>
    parameter::parameter(const names& alias, std::tuple<Ts...>& ref) : parameter_base(alias, std::tuple_size<std::tuple<Ts...>>::value) {
        parser_ = create_parser(ref, arity_);
    }
    
    parameter::parameter(const names& alias, int arity, parser parse) : parameter_base(alias, arity), parser_(parse) {
        validate_arity_or_throw();
    }
    
    vparameter::vparameter(const names& alias, parser parse) : parameter_base(alias, 0), parser_(parse) {
    }
    
    void parameter::validate_arity_or_throw() {
        if (arity_ < 0) {
            throw invalid_arity();
        }
    }
}
